package Image;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Vector;
import javax.imageio.ImageIO;
import Image.segment.Segment;
import Image.segment.Segmenter;

/**
 * This class represents a collection of images on the file system:
 * the original image, the segment map/mask image, and the segment image.
 *
 * Original Image
 *	is an image which contains no segment information.
 * Segment Map/Mask Image
 *	is a gif file which masks off each segment where the index of the color is the number
 *	of the segment. This file must be in the same folder as the original image and be
 *	prefixed with the name of the original image minus the extension and suffixed with
 *	.map.gif
 * Segment Image
 *	is the original image with segment boundaries drawn in. This file must be in the same
 *	folder as the original image and be prefixed with the name of the original image and
 *	suffixed with .out.jpg
 *
 * To clarify "... prefixed with the name of the original image and suffixed with ..."
 *    If the original image name is "My.Original.jpg" then the Segment Map/Segment Image
 *    should have names My.Original.map.gif/My.Original.out.jpg
 *
 * @author Seth Wessitsh <seth@wessitsh.com>
 */
public class SegmentedImage {
	File imageFile = null;
	File segmentMapFile = null;
	File segmentImageFile = null;
	BufferedImage image = null;
	BufferedImage segmentMap = null;
	BufferedImage segmentImage = null;
	Vector<Segment> segments = new Vector<Segment>();
	Vector<String> words = new Vector<String>();

	static {
		System.setProperty("com.sun.media.jai.disableMediaLib", "true");
	}

	/**
	 * For debugging purposes
	 * @param args
	 */
	public static void main(String[] args) {
		Vector<String> words = new Vector<String>();
		words.add("Tree");
		words.add("Sky");
		words.add("Tree");
		words.add("Sky");
		words.add("Sky");
		words.add("Cloud");
		words.add("Grass");

		SegmentedImage img = new SegmentedImage(
			new File(
				"C:/Users/Seth/Documents/Programming/ImageAnnotation/Images/Image4.jpg"
			),
			words
		);

		int i = 1;
		int k = 1;
		Vector<Segment> segments = img.getSegments();
		for (Segment s : segments) {
			Vector<Double[]> features = s.getFeatures();
			System.out.println("Segment " + i++ + " Features:");

			System.out.println(
				"\tIntensity: " + features.get(0)[0] + "\n" +
				"\tHue: " + features.get(0)[1] + "\n" +
				"\tSaturation: " + features.get(0)[2] + "\n" +
				"\taspect: " + features.get(1)[0] + "\n" +
				"\trectangularity: " + features.get(2)[0] + "\n" +
				"\tleft: " + features.get(3)[0] + "\n" +
				"\ttop: " + features.get(3)[1] + "\n"
			);

			/**
			 * Write a file for each segment mask
			 *
			File out = new File(
				"C:/temp/extract/jseg/inputImage.seg[" + k++ + "].gif");

			try {
				ImageIO.write(s.getAsImage(), "gif", out);
			} catch (IOException ioe) {
				System.out.println(
					"Unable to write (" + out.getAbsolutePath() + ").");
			}
			*/
		}
		for (i = 0; i < segments.size(); i++) {
			Segment s = segments.get(i);
			for (int j = 0; j < segments.size(); j++) {
				System.out.println(
					"Sim(s" + (i + 1) + ", s" + (j + 1) + "): " +
					s.getSimilarity(segments.get(j), true)
				);
			}
		}
	}

	/**
	 * Create a new SegmentedImage from the File (@code image)
	 * who's Segments have the words in (@code wordsVector)
	 * associated with them.
	 * 
	 * @param image the file to create the SegmentedImage object from
	 * @param wordsVector the words to associate with each segment
	 *                     index 0 is associated with the first segment
	 *                     index 1 with the second and so on.
	 */
	public SegmentedImage(File image, Vector<String> wordsVector) {
		imageFile = image;
		String directory =
			imageFile.getParentFile().getAbsolutePath() + File.separator;
		String fileName = imageFile.getName();
		String fileNamePrefix = fileName.substring(0, fileName.indexOf("."));

		segmentImageFile = new File(directory + fileNamePrefix + ".out.jpg");

		if (wordsVector != null) {
			words = wordsVector;
		}
		loadImage();
		loadSegmentMap();
		segment();
	}

	/**
	 * @return the File object pointing to the image this SegmentedImage was created from
	 */
	public File getImageFile() {
		return imageFile;
	}

	/**
	 * @param originalFile the File object this SegmentedImage should be created from
	 */
	public void setImageFile(File originalFile) {
		this.imageFile = originalFile;
	}

	/**
	 * @return a File object pointing to an image which has the segments overlayed
	 *          so they can be seen visually.
	 */
	public File getSegmentImageFile() {
		return segmentImageFile;
	}

	/**
	 * @return a File object pointing to an image which is the segment map/mask
	 */
	public File getSegmentMapFile() {
		return segmentMapFile;
	}

	/**
	 * @param segmentMapFile a File object pointing to an image which is the map/mask
	 *                        defining the segments within the original image.
	 */
	public void setSegmentMapFile(File segmentMapFile) {
		this.segmentMapFile = segmentMapFile;
	}

	/**
	 * @return the original image
	 */
	public BufferedImage getImage() {
		return image;
	}

	/**
	 * @return the original image with words drawn to corresponding segments
	 */
	public BufferedImage getImageWithWords() {
		if (words.size() > 0) {
			return drawWordsToImage(image);
		} else {
			return image;
		}
	}

	/**
	 * @return the original image with segments overlayed
	 */
	public BufferedImage getSegmentImage() {
		if (segmentImage == null) {
			try {
				segmentImage = ImageIO.read(segmentImageFile);
			} catch (IOException ignored) {
				segmentImage = null;
			}
		}
		return segmentImage;
	}

	/**
	 * @return the original image with segments and corresponding words overlayed
	 */
	public BufferedImage getSegmentImageWithWords() {
		if (words.size() > 0) {
			return drawWordsToImage(getSegmentImage());
		} else {
			return getSegmentImage();
		}
	}

	/**
	 * @return the segment map/mask defining the segments within the original image
	 */
	public BufferedImage getSegmentMap() {
		return segmentMap;
	}

	/**
	 * @param segmentMap set the map/mask defining the segments within the orignal image
	 */
	public void setSegmentMap(BufferedImage segmentMap) {
		this.segmentMap = segmentMap;
	}

	/**
	 * @return a collection of Segment objects which correspond to the segments in the
	 *          image
	 */
	public Vector<Segment> getSegments() {
		return segments;
	}

	/**
	 * @param segments the collection of Segment objects which correspond to the segments
	 *         in the image
	 */
	public void setSegments(Vector<Segment> segments) {
		this.segments = segments;
	}

	/**
	 * Get the ith Segment object
	 *
	 * @param i the index of the Segment to get
	 * @return the Segment object corresponding to index i, an exception is thrown
	 *          if i is out of bounds.
	 */
	public Segment getSegment(int i) {
		return segments.get(i);
	}

	/**
	 * @return a collection of words corresponding to the segments in this image
	 */
	public Vector<String> getWords() {
		return words;
	}

	/**
	 * @param wordsVector a collection of words corresponding to the segments in this
	 *                     image
	 */
	public void setWords(Vector<String> wordsVector) {
		if (words != null) {
			words = wordsVector;
		}
	}

	/**
	 * Get the ith word which corresponds to the ith segment in this image
	 *
	 * @param i the index of the word to get
	 * @return the word corresponding to segment i
	 */
	public String getWord(int i) {
		if (i < 0 || i >= words.size()) {
			return "";
		} else {
			return words.get(i);
		}
	}

	/**
	 * Loads the original image from the file system as a BufferedImage and converts
	 * its ColorSpace to the IHS ColorSpace.
	 *
	 * Note the IHS/HSI/HSL ColorSpace does not correspond to HSB/HSV
	 */
	private void loadImage() {
		/**
		 * Load the image file and then convert it to the IHS/HSL ColorSpace
		 */
		try {
			BufferedImage temp = ImageIO.read(imageFile);
			image = ImageUtils.getIHSImage(temp);
		} catch (IOException ioe) {
			// unable to read file, time to blow up
		}
	}

	/**
	 * Attempts to load the segment map/mask image from the file system as a BufferedImage
	 * the file name must be in the same directory as the original image and have the
	 * same prefix but the suffix must be .map.gif.
	 *
	 * E.g.,
	 *	If the original image file name is: c:\temp\original.jpg
	 *  The map file name must be: c:\temp\original.map.gif
	 *
	 * This method does nothing if the map cannot be loaded as it is assumed that
	 * Segmenter.createSegments will create it if it is not available at this time.
	 */
	private void loadSegmentMap() {
		String directory = imageFile.getParentFile().getAbsolutePath();
		String fileName = imageFile.getName();
		String fileNamePrefix = fileName.substring(0, fileName.indexOf("."));

		segmentMapFile = new File(
			directory + File.separator + fileNamePrefix + ".map.gif");

		/**
		 * try to load the map file
		 * no need to do anything if it doesn't load.  The
		 * Segmenter.createSegments method will create the file
		 * and add it if necessary
		 */
		try {
			System.out.println("Loading (" + segmentMapFile.getAbsolutePath() + ").");
			segmentMap = ImageIO.read(segmentMapFile);
		} catch (IOException ioe) {
			System.out.println("Load failed.");
		}
	}

	/**
	 * Created a new buffered image from that provided which has the words corresponding
	 * to the segments in the image overlayed.
	 *
	 * @param image the image to be copied
	 * @return a copy of <code>image</code> with words, corresponding to the
	 *          segments in the image, overlayed.
	 */
	private BufferedImage drawWordsToImage(BufferedImage image) {
		BufferedImage out = new BufferedImage(
			image.getWidth(), image.getHeight(), image.getType());

		Graphics2D g2 = (Graphics2D) out.getGraphics();
		g2.drawImage(image, 0, 0, null);

		Font cf = g2.getFont();
		g2.setFont(new Font(
			cf.getName(), cf.getStyle(), (int)(image.getHeight()*0.05)
		));

		// get metrics from the graphics
		FontMetrics metrics = g2.getFontMetrics(g2.getFont());

		for (int i = 0; i < words.size(); i++) {
			String word = words.get(i);
			Point location = segments.get(i).getCentroid();

			// get the height of a line of text in this font and render context
			int height = metrics.getHeight();
			// get the advance of my text in this font and render context
			int width = metrics.stringWidth(word);

			g2.setColor(Color.black);
			g2.drawString(
				word,
				(location.x - width / 2) + 1,
				(location.y + height / 2) + 1
			);
			g2.setColor(Color.white);
			g2.drawString(
				word,
				location.x - width / 2,
				location.y + height / 2
			);
		}

		return out;
	}

	/**
	 * Called to segment the image
	 * delegates to {@link Segmenter#createSegments(Image.SegmentedImage)}
	 */
	private void segment() {
		Segmenter.createSegments(this);
	}
}
